﻿//-----------------------------------------------------------------------
// <copyright file="SP0014 - ClosePublishingWebReturnedFromPublishingWebGetPublishingWebsBracketsIndexOperator.cs" company="Adrien Siffermann">
//      Copyright © Adrien Siffermann 2008-2009. All rights reserved.
// </copyright>
// <summary>This file contains implementation of the ClosePublishingWebReturnedFromPublishingWebGetPublishingWebsBracketsIndexOperator class.</summary>
// <website>http://sovfxcoprules.codeplex.com/</website>
//-----------------------------------------------------------------------

namespace SharePoint.FxCop.BestPracticesRules
{
    using System;
    using System.Linq;
    using Microsoft.FxCop.Sdk;

    /// <summary>
    /// Defines a custom Code Analysis rule to check if the
    /// Dispose pattern is respected in the SharePoint Object Model
    /// when using the PublishingWeb.GetPublishingWebs [] index operator.
    /// </summary>
    public class ClosePublishingWebReturnedFromPublishingWebGetPublishingWebsBracketsIndexOperator : BaseStaticAnalysisRule
    {
        #region Constructors
        /// <summary>
        /// Initializes a new instance of the <see cref="ClosePublishingWebReturnedFromPublishingWebGetPublishingWebsBracketsIndexOperator"/> class.
        /// </summary>
        public ClosePublishingWebReturnedFromPublishingWebGetPublishingWebsBracketsIndexOperator()
            : base("ClosePublishingWebReturnedFromPublishingWebGetPublishingWebsBracketsIndexOperator")
        {
        }
        #endregion

        #region Public Methods
        /// <summary>
        /// Checks the specified member.
        /// </summary>
        /// <param name="member">The member.</param>
        /// <returns>The collection of problems.</returns>
        public override ProblemCollection Check(Member member)
        {
            if (!BaseStaticAnalysisRule.CheckMember(member))
            {
                return null;
            }

            Method method = member as Method;
            this.InitWorkingVars();
            this.LastProperty = String.Empty;
            this.VarsToWatch.Clear();

            foreach (Instruction instruction in method.Instructions)
            {
                switch (instruction.OpCode)
                {
                    case OpCode.Newobj:
                        break;
                    case OpCode.Stloc_0:
                    case OpCode.Stloc_1:
                    case OpCode.Stloc_2:
                    case OpCode.Stloc_3:
                    case OpCode.Stloc_S:
                        this.CheckStLoc(instruction);
                        break;
                    case OpCode.Ldloc_0:
                    case OpCode.Ldloc_1:
                    case OpCode.Ldloc_2:
                    case OpCode.Ldloc_3:
                    case OpCode.Ldloc_S:
                        this.CheckLdLoc(instruction);
                        break;
                    case OpCode.Call:
                    case OpCode.Callvirt:
                    case OpCode.Jmp:
                        this.CheckCall(instruction);
                        break;
                    case OpCode.Castclass:
                        this.CheckCast(instruction);
                        break;
                    case OpCode.Ldstr:
                        break;
                    default:
                        this.InitWorkingVars();
                        break;
                }
            }

            this.AddProblems(method);
            return Problems;
        }
        #endregion

        #region Private Methods
        private void CheckStLoc(Instruction instruction)
        {
            if (this.ForEachInProgress || this.IsCallingSpecialMethod || this.IsAccessingProperty)
            {
                Local varStored = instruction.Value as Local;
                if (varStored.Type.FullName == "Microsoft.SharePoint.Publishing.PublishingWebCollection")
                {
                    this.VarsToWatch.Add(new WatchedVar() { Name = varStored.Name.Name, TypeFullName = varStored.Type.FullName, TargetedNode = instruction, IsDisposed = true });
                }
                else if (varStored.Type.FullName == "Microsoft.SharePoint.Publishing.PublishingWeb")
                {
                    this.VarsToWatch.Add(new WatchedVar() { Name = varStored.Name.Name, TypeFullName = varStored.Type.FullName, TargetedNode = instruction, ResolutionName = "PublishingWeb" });
                }
                else
                {
                    this.VarsToWatch.Add(new WatchedVar() { Name = varStored.Name.Name, TypeFullName = varStored.Type.FullName, TargetedNode = instruction });
                }

                this.IsCallingSpecialMethod = false;
                this.ForEachInProgress = false;
                this.IsAccessingProperty = false;
            }
        }

        private void CheckLdLoc(Instruction instruction)
        {
            Local varLoaded = instruction.Value as Local;
            WatchedVar watchedVarLoaded = this.VarsToWatch.FirstOrDefault(v => v.Name == varLoaded.Name.Name);
            if (watchedVarLoaded != null)
            {
                this.IsInvoking = true;
                this.CurrentVar = watchedVarLoaded;
            }
        }

        private void CheckCall(Instruction instruction)
        {
            Method callee = instruction.Value as Method;
            string calleeName = callee.FullName;
            
            int j = calleeName.LastIndexOf('.');
            if (j > 0)
            {
                calleeName = calleeName.Substring(j + 1);
            }
            
            this.CheckSpecialMethodInvoking(callee.FullName);
            
            if (this.IsInvoking == true)
            {
                this.CheckDisposeCall(calleeName);
                this.IsInvoking = false;
            }
            else if (this.IsAccessingProperty)
            {
                this.CheckPropertyCalled(calleeName);
                this.IsAccessingProperty = false;
            }
        }

        private void CheckCast(Instruction instruction)
        {
            if (this.ForEachInProgress)
            {
                ClassNode node = instruction.Value as ClassNode;
                if (node == null || node.FullName != "Microsoft.SharePoint.Publishing.PublishingWeb")
                {
                    this.ForEachInProgress = false;
                }
            }
        }

        private void CheckSpecialMethodInvoking(string calleeFullName)
        {
            if (calleeFullName.StartsWith("Microsoft.SharePoint.Publishing.PublishingWeb.GetPublishingWebs", StringComparison.Ordinal))
            {
                this.IsCallingSpecialMethod = true;
            }
            else if (calleeFullName.StartsWith("System.Collections.IEnumerator.get_Current", StringComparison.Ordinal))
            {
                this.ForEachInProgress = true;
            }
        }
        #endregion
    }
}
